ward

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Jun 21, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

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:

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

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

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

View Source
const DefaultTimeout = 3 * time.Second

DefaultTimeout is the default alert display duration.

Variables

This section is empty.

Functions

func RenderCommandBar

func RenderCommandBar(target string, hints []KeyHint, cols int, hasAlert bool) string

RenderCommandBar renders the command mode bar with key hints and optional target. hasAlert controls whether RequireAlert hints are shown.

func RenderStatusBar

func RenderStatusBar(message string, cols int, alert bool) string

RenderStatusBar renders a full-width status bar with the given message. alert selects the orange alert style; otherwise the cyan default style is used.

Types

type Event

type Event int

Event represents an OSC 133 semantic prompt marker.

const (
	EventPromptStart     Event = iota // ESC ] 133 ; A ST
	EventCommandStart                 // ESC ] 133 ; B ST
	EventCommandExecuted              // ESC ] 133 ; C ST
	EventCommandFinished              // ESC ] 133 ; D [; exit_code] ST
)

type KeyHint

type KeyHint struct {
	Key          byte
	Desc         string
	RequireAlert bool
}

KeyHint defines an action key shown on the bar during command mode.

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

func NewWrapper(opts Options) *Wrapper

NewWrapper creates a new Wrapper with the given options.

func (*Wrapper) Run

func (w *Wrapper) Run(ctx context.Context) (int, error)

Run starts the child process in a PTY and manages I/O until it exits. Returns the child's exit code and any error.

type Zone

type Zone int

Zone represents the current shell lifecycle phase.

const (
	ZoneUnknown Zone = iota // No marker seen, or after D
	ZonePrompt              // Between A and B
	ZoneInput               // Between B and C
	ZoneOutput              // Between C and D
)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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