Documentation
¶
Overview ¶
Package ward wraps a child process in a pseudo-terminal and reserves the last terminal row for a notification bar.
Two data paths (input and output) plus a status channel feed into a single event loop that owns the bar. The stdin path includes a two-state machine (normal/command) that intercepts the hotkey (Ctrl+], 0x1D) when OnKey is configured and stdin is a TTY.
Input: user keystrokes → child stdin
┌──────┐ ┌──────────┐ stdin.Read() ┌────────┐ ┌───────┐
│ user │ os.Stdin │ │ ──────────────► │ PTY │ ──► │ child │
│ term │ ─────────► │ Wrapper │ ptmx.Write() │ master │ │ stdin │
└──────┘ │ │ └────────┘ └───────┘
└──────────┘
Output: child stdout → user terminal (+ escape sequence scanning)
┌───────┐ ┌────────┐ ┌──────────────┐ ┌───────────┐ ┌──────┐
│ child │ ─► │ PTY │ ──► │ output │ ──► │ escScanner│ ──► │ user │
│stdout │ │ master │ │ goroutine │ │ .Scan() │ │ term │
└───────┘ └────────┘ │ ptmx.Read() │ └─────┬─────┘ └──────┘
└──────────────┘ │
│ detects:
│ • ESC c (RIS)
│ • CSI r (no params)
│ • CSI J / 2J / 3J
▼
┌─────────────────┐
│ re-emit scroll │
│ region + repaint│
│ bar if needed │
└─────────────────┘
Notifications: status channel feeds the event loop
┌───────────┐
│ Status │ barEventStatus / barEventAlert
│ channel │ ─────────────────┐
│ (Go chan) │ │
└───────────┘ ▼
┌────────────────────────────────────────────────┐
│ event loop │
│ │
│ • barEventStatus → update lastStatus, │
│ show if no active alert │
│ • barEventAlert → show immediately or queue │
│ (max 64), start dismiss timer │
│ • barEventDismiss → pop next alert or fall │
│ back to lastStatus or clear │
│ │
│ sole owner of bar content; writes to stdout │
│ under outputMu │
└──────────────────────┬─────────────────────────┘
│
▼
┌────────────────────────────────────────────────┐
│ terminal last row │
│ ╱╱╱ VIBEPIT message ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ │
└────────────────────────────────────────────────┘
Command mode lifecycle (stdin goroutine ↔ event loop):
stdin goroutine event loop
─────────────── ──────────
detect 0x1D
→ barEventEnterCommand ────────► commandGen++
← commandResponse ◄──────────── stop dismiss timer
start idle timer
render command bar
(waiting for key)
matched key pressed
→ barEventBeginAction ─────────► stop idle timer
← ack (true) ◄────────────────── enter pendingAction
call OnKey(ctx, key, target)
→ barEventAction ──────────────► flash result
dismiss alert
process queue
Terminal protection via DECSTBM scroll region:
row 1 ┌────────────────────────┐
│ │ ◄── scroll region: rows 1..(height-1)
│ child output area │ child's PTY size = height-1 rows
│ │ normal scrolling confined here
row N-1 │ │
├────────────────────────┤
row N │ ward notification bar │ ◄── protected: outside scroll region
└────────────────────────┘
Three things worth calling out:
The scroll region is the primary protection mechanism. Ward sets DECSTBM to rows 1..N-1 so normal output and scrolling cannot reach row N. The child's PTY is sized to N-1 rows, so it believes the terminal is one row shorter than reality. Ward re-applies the scroll region after any detected reset (ESC c, parameterless CSI r).
The escScanner is intentionally minimal — not a terminal emulator. It recognizes just enough ECMA-48/DEC grammar to detect the three sequence classes that can destroy the bar (reset, margin clear, screen erase). It also tracks whether the byte stream is mid-sequence so ward never injects its own escapes inside the child's incomplete sequences.
The bar is not repainted on every PTY read. Doing so would leak bar escape sequences into terminal scrollback. Repainting occurs only on three occasions: when the escScanner detects a scroll/erase reset, when the event loop changes bar content, and when SIGWINCH fires.
Index ¶
Constants ¶
const DefaultTimeout = 3 * time.Second
DefaultTimeout is the default alert display duration.
Variables ¶
This section is empty.
Functions ¶
func RenderCommandBar ¶
RenderCommandBar renders the command mode bar with key hints and optional target. hasAlert controls whether RequireAlert hints are shown.
Types ¶
type OSC133Parser ¶
type OSC133Parser struct {
// contains filtered or unexported fields
}
OSC133Parser is a streaming, zero-allocation parser for OSC 133 escape sequences.
func NewOSC133Parser ¶
func NewOSC133Parser() *OSC133Parser
NewOSC133Parser creates a parser in the initial ground/unknown state.
func (*OSC133Parser) Push ¶
func (p *OSC133Parser) Push(data []byte, onEvent func(Event, *int))
Push processes a chunk of bytes, calling onEvent for every OSC 133 marker found. The caller is responsible for forwarding bytes to their destination.
func (*OSC133Parser) Zone ¶
func (p *OSC133Parser) Zone() Zone
Zone returns the current semantic zone.
type Options ¶
type Options struct {
Command []string
Hotkey byte // default 0x1D = Ctrl+]
Env []string // extra KEY=VALUE pairs for the child process
Status <-chan StatusUpdate // nil-safe; bar stays hidden until first event
OnKey func(ctx context.Context, key byte, target string) (string, error)
}
Options configures the PTY wrapper.
type StatusUpdate ¶
type StatusUpdate struct {
Message string
Alert bool
Timeout time.Duration
Target string
KeyHints []KeyHint
}
StatusUpdate carries a message, alert flag, and display timeout for the status bar.
type Wrapper ¶
type Wrapper struct {
// contains filtered or unexported fields
}
Wrapper is the core PTY wrapper that runs a child process in a pseudo-terminal, manages toast notifications, and handles terminal resizing.
func NewWrapper ¶
NewWrapper creates a new Wrapper with the given options.