runfx

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Mar 24, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package runfx provides a robust, efficient, and highly responsive optional runtime loop for advanced terminal (TTY) management, multiplexed visuals, graceful degradation, and detailed observability.

RunFX is designed for explicit mounting of visuals, zero hidden dependencies, and advanced features to ensure reliability in complex CLI applications.

Key Features

- Explicit TTY ownership with advanced multiplexing - Cross-platform signal handling (SIGWINCH on Unix, graceful fallback on Windows) - Double-buffered, flicker-free rendering - Thread-safe visual mounting/unmounting - Configurable tick rates for smooth animation (30-120ms) - Intelligent fallback for non-TTY environments - Zero reflection, global state, or hidden dependencies - Multipath API with three entry points for different usage patterns

Multipath API Usage

RunFX follows TFX's multipath API philosophy with three distinct paths:

## Beginner Path - Simple & Convenient

Zero-config (uses sensible defaults):

loop := runfx.Start()

Config struct (explicit configuration):

cfg := runfx.Config{
	TickInterval: 30 * time.Millisecond,
	TestMode:     true,
}
loop := runfx.Start(cfg)

## Hardcore Path - Fluent DSL Builder

Declarative chaining for maximum expressiveness:

loop := runfx.New().
	SmoothAnimation().        // 30ms tick interval
	TestMode().
	Output(os.Stderr).
	Start()

Available builder methods:

  • TickInterval(duration) - Custom tick rate
  • SmoothAnimation() - 30ms ticks for smooth animations
  • FastAnimation() - 100ms ticks for less CPU usage
  • TestMode() - Enable test mode for non-TTY environments
  • Output(writer) - Custom output destination

## Experimental Path - Functional Options

Note: This path is experimental and not currently in active use:

loop := runfx.StartWith(
	runfx.WithSmoothAnimation(),
	runfx.WithTestMode(),
)

Basic Usage Pattern

Standard workflow with any of the above creation methods:

// Create loop (using any method above)
loop := runfx.Start()  // or runfx.New().SmoothAnimation().Start()

// Mount your visual
unmount, err := loop.Mount(myVisual)
if err != nil {
	return err
}
defer unmount()

// Run with context
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

return loop.Run(ctx)

Visual Interface

Any type implementing the Visual interface can be mounted:

type Visual interface {
	Render(w share.Writer)     // Called during render cycles
	Tick(now time.Time)        // Called on each tick for animations
	OnResize(cols, rows int)   // Called when terminal is resized
}

Graceful Degradation

RunFX automatically detects TTY capabilities and falls back to minimal output in non-TTY environments, ensuring reliability across different deployment scenarios.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrLoopClosed         = errors.New("runfx: event loop is closed")
	ErrMountFailed        = errors.New("runfx: failed to mount visual")
	ErrNotTTY             = errors.New("runfx: not a TTY environment")
	ErrLoopAlreadyRunning = errors.New("runfx: loop is already running")
	ErrLoopNotRunning     = errors.New("runfx: loop is not running")
)

Functions

func DebugLog

func DebugLog(format string, args ...any)

DebugLog prints debug info if debug mode is enabled

func EnableDebug

func EnableDebug()

EnableDebug turns on verbose terminal state logging

func FallbackOutput

func FallbackOutput(msg string)

FallbackOutput prints minimal output if not TTY

func LogFlush

func LogFlush(tickCount int, elapsed time.Duration)

func LogMount

func LogMount(name string)

LogMount logs visual mounting

func LogRender

func LogRender(name string)

LogRender logs rendering events

func LogTick

func LogTick(tickCount, count int, elapsed time.Duration)

func LogUnmount

func LogUnmount(name string)

LogUnmount logs visual unmounting

func WithAutoTick

func WithAutoTick() share.Option[Config]

WithAutoTick returns an Option to set the tick interval based on detected TTY capabilities.

func WithFastAnimation

func WithFastAnimation() share.Option[Config]

WithFastAnimation returns an Option to set a 100ms tick interval for efficient animations.

func WithInput

func WithInput(input io.Reader) share.Option[Config]

WithInput returns an Option to set a custom input source.

func WithOutput

func WithOutput(output io.Writer) share.Option[Config]

WithOutput returns an Option to set a custom output writer.

func WithSmoothAnimation

func WithSmoothAnimation() share.Option[Config]

WithSmoothAnimation returns an Option to set a 30ms tick interval for smooth animations.

func WithTickInterval

func WithTickInterval(interval time.Duration) share.Option[Config]

WithTickInterval returns an Option to set a custom tick interval.

Types

type Config

type Config struct {
	TickInterval time.Duration
	Output       io.Writer
	Input        io.Reader
}

Config provides structured configuration for RunFX Loop

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns default configuration for RunFX

type Interactive

type Interactive interface {
	Visual
	OnKey(key Key) bool // Returns true to stop the loop.
}

Interactive is a Visual that can respond to keyboard input.

type Key

type Key struct {
	Code     KeyCode
	Modifier Modifier
	Rune     rune // useful for printable keys
}

Key represents a single key press, with optional modifier info.

func (Key) IsAccept

func (k Key) IsAccept() bool

IsAccept returns true if the key is an accept key (Enter, Space).

func (Key) IsArrow

func (k Key) IsArrow() bool

IsArrow returns true if it's an arrow key.

func (Key) IsCancel

func (k Key) IsCancel() bool

IsCancel returns true if the key is a cancel key (Escape or Ctrl+C).

func (Key) IsNavigation

func (k Key) IsNavigation() bool

IsNavigation returns true if the key is a navigation key (Arrow keys, WASD, Tab).

func (Key) IsNumber

func (k Key) IsNumber() bool

IsNumber returns true if it's Key0–Key9

func (Key) IsPrintable

func (k Key) IsPrintable() bool

func (Key) IsSelector

func (k Key) IsSelector() bool

IsSelector returns true if the key is used to select an option (navigation, accept or cancel).

func (Key) IsWASD

func (k Key) IsWASD() bool

IsWASD returns true if the key is W/A/S/D

func (Key) ToNumber

func (k Key) ToNumber() int

ToNumber returns the number, or -1 if not numeric

type KeyCode

type KeyCode int

KeyCode represents a keyboard input event code.

const (
	KeyUnknown KeyCode = iota

	// Special keys
	KeyEnter
	KeyEscape
	KeyBackspace
	KeyTab
	KeySpace
	KeyDelete

	// Arrow keys
	KeyArrowUp
	KeyArrowDown
	KeyArrowLeft
	KeyArrowRight

	// Letter keys
	KeyA
	KeyB
	KeyC
	KeyD
	KeyE
	KeyF
	KeyG
	KeyH
	KeyI
	KeyJ
	KeyK
	KeyL
	KeyM
	KeyN
	KeyO
	KeyP
	KeyQ
	KeyR
	KeyS
	KeyT
	KeyU
	KeyV
	KeyW
	KeyX
	KeyY
	KeyZ

	// Number keys
	Key0
	Key1
	Key2
	Key3
	Key4
	Key5
	Key6
	Key7
	Key8
	Key9

	// Shortcuts
	KeyCtrlC
	KeyCtrlD
	KeyCtrlZ
)

func (KeyCode) String

func (k KeyCode) String() string

String returns a readable name for debugging/logging.

type KeyReader

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

KeyReader handles reading keyboard input from a terminal and converting it to Key events.

func NewKeyReader

func NewKeyReader(input io.Reader) *KeyReader

NewKeyReader creates a new keyboard input reader.

func (*KeyReader) ReadKey

func (kr *KeyReader) ReadKey(ctx context.Context) (Key, error)

ReadKey reads the next keyboard input and returns the corresponding Key.

type Loop

type Loop interface {
	Mount(v Visual) (unmount func(), err error)
	Run(ctx context.Context) error
	Stop() error
	IsRunning() bool
}

Loop defines the runtime loop for mounting and managing visuals.

func MustStart added in v0.2.0

func MustStart(opts ...any) Loop

MustStart creates a new Loop with multipath configuration support and panics on invalid multipath input.

Prefer TryStart when configuration may be user-provided or otherwise fallible.

func Start

func Start(opts ...any) Loop

Start creates and starts a new Loop with multipath configuration support.

Start is kept as a zero-ceremony compatibility alias for MustStart. Prefer TryStart for fallible input or MustStart when panic semantics should be explicit at the callsite.

func StartWith

func StartWith(cfg Config) Loop

StartWith creates and returns a new Loop configured with the provided functional options. It is the primary entry point for the Functional Options path.

func TryStart

func TryStart(opts ...any) (Loop, error)

TryStart creates a new Loop with multipath configuration support without panicking.

type LoopBuilder

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

LoopBuilder provides a fluent DSL interface for configuring RunFX loops

func New

func New() *LoopBuilder

New creates a new LoopBuilder with default configuration. HARDCORE path - provides fluent DSL interface:

runfx.New().TickInterval(100*time.Millisecond).TestMode(true).Start()

func (*LoopBuilder) AutoTick

func (b *LoopBuilder) AutoTick() *LoopBuilder

func (*LoopBuilder) FastAnimation

func (b *LoopBuilder) FastAnimation() *LoopBuilder

FastAnimation sets tick interval to 100ms for faster/less resource intensive animations

func (*LoopBuilder) Input

func (b *LoopBuilder) Input(input io.Reader) *LoopBuilder

Input sets the keyboard input source.

func (*LoopBuilder) Output

func (b *LoopBuilder) Output(output io.Writer) *LoopBuilder

Output sets the output writer

func (*LoopBuilder) SmoothAnimation

func (b *LoopBuilder) SmoothAnimation() *LoopBuilder

SmoothAnimation sets tick interval to 30ms for very smooth animations

func (*LoopBuilder) Start

func (b *LoopBuilder) Start() Loop

Start creates and returns the configured Loop instance

func (*LoopBuilder) TickInterval

func (b *LoopBuilder) TickInterval(interval time.Duration) *LoopBuilder

TickInterval sets the tick interval for the event loop

type MainLoop

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

MainLoop is handles terminal I/O, signals, and the render/tick cycle.

func (*MainLoop) IsRunning

func (ml *MainLoop) IsRunning() bool

IsRunning checks if the loop is currently active.

func (*MainLoop) Mount

func (ml *MainLoop) Mount(v Visual) (unmount func(), err error)

Mount registers a visual component with the loop's multiplexer.

func (*MainLoop) Run

func (ml *MainLoop) Run(ctx context.Context) error

Run starts the main loop and blocks until the context is canceled or Stop() is called.

func (*MainLoop) Stop

func (ml *MainLoop) Stop() error

Stop gracefully shuts down the main loop.

type Modifier

type Modifier uint8
const (
	ModNone Modifier = 0
	ModCtrl Modifier = 1 << iota
	ModAlt
	ModShift
)

func (Modifier) Has

func (m Modifier) Has(mod Modifier) bool

func (Modifier) String

func (m Modifier) String() string

type Multiplexer

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

Multiplexer safely manages a set of visual components. Now uses an atomic counter to generate unique IDs.

func NewMultiplexer

func NewMultiplexer() *Multiplexer

NewMultiplexer creates a new instance of the multiplexer.

func (*Multiplexer) GetVisual

func (m *Multiplexer) GetVisual(id VisualID) (Visual, bool)

GetVisual retrieves a visual component by its ID.

func (*Multiplexer) ListVisuals

func (m *Multiplexer) ListVisuals() []VisualID

ListVisuals returns a list of the IDs of all mounted components.

func (*Multiplexer) Mount

func (m *Multiplexer) Mount(v Visual) VisualID

Mount registers a new visual component and assigns it a unique ID. Returns the assigned ID.

func (*Multiplexer) OnResize

func (m *Multiplexer) OnResize(cols, rows int)

OnResize notifies all visual components of a terminal resize event.

func (*Multiplexer) Render

func (m *Multiplexer) Render() []byte

Render adds the bytes of all visual components for a single frame.

func (*Multiplexer) Unmount

func (m *Multiplexer) Unmount(id VisualID)

Unmount removes a visual component using its ID.

type TTYInfo

type TTYInfo struct {
	IsTTY     bool
	TrueColor bool
	ANSI      bool
	NoColor   bool
}

TTYInfo holds terminal capability details (uses writer.TerminalWriter for consistency)

func DetectTTY

func DetectTTY() TTYInfo

DetectTTY returns TTYInfo using the same detection logic as writer.TerminalWriter

func DetectTTYForIO

func DetectTTYForIO(output io.Writer, input io.Reader) TTYInfo

DetectTTYForIO returns TTYInfo for the controlling terminal device selected from the provided output/input pair.

func DetectTTYForOutput

func DetectTTYForOutput(output io.Writer) TTYInfo

DetectTTYForOutput returns TTYInfo for a specific output writer

type Updatable

type Updatable interface {
	Visual
	Tick(now time.Time)
}

Updatable define el hook opcional de tick.

type Visual

type Visual interface {
	Render() []byte
	OnResize(cols, rows int)
}

type VisualID

type VisualID uint64

VisualID is a unique and opaque identifier for a mounted visual component.

Jump to

Keyboard shortcuts

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