ptyio

package
v0.0.20 Latest Latest
Warning

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

Go to latest
Published: Jul 17, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package ptyio holds the low-level PTY/tmux plumbing shared by the center and sidebar terminals: the PTY read loop and output forwarding/merging, PTY-noise filtering and overflow trimming, and tmux session bootstrap/restore (capture, snapshot, scrollback). It was split out of internal/ui/common so panes that do no terminal I/O don't transitively depend on tmux/vterm/safego through it.

Index

Constants

View Source
const (
	// RestartBackoffInitial is the first reader-restart delay.
	RestartBackoffInitial = 200 * time.Millisecond
	// RestartBackoffCap bounds the exponential reader-restart delay.
	RestartBackoffCap = 5 * time.Second
)

Reader-restart backoff policy: restarts are counted within a rolling window; each retry doubles the delay from RestartBackoffInitial up to RestartBackoffCap, and once the per-window restart budget is exhausted the caller should stop restarting and mark the terminal detached.

View Source
const (
	// PtyFlushQuiet is the quiet period output must be idle for before a steady
	// flush fires.
	PtyFlushQuiet = 12 * time.Millisecond
	// PtyFlushChunkSize bounds the bytes drained per steady-state flush.
	PtyFlushChunkSize = 32 * 1024
	// PtyReadBufferSize is the size of the PTY reader's read buffer.
	PtyReadBufferSize = 32 * 1024
	// PtyFrameInterval is the render cadence (24 fps) for PTY output.
	PtyFrameInterval = time.Second / 24
	// PtyReaderStallTimeout is how long a reader may go silent before it is
	// treated as stalled.
	PtyReaderStallTimeout = 10 * time.Second
	// PtyRestartMax is the max reader restarts allowed within PtyRestartWindow.
	PtyRestartMax = 5
	// PtyRestartWindow is the sliding window for counting reader restarts.
	PtyRestartWindow = time.Minute
)

Shared PTY flush/buffer tuning constants used by both the center (agent tabs) and sidebar (single terminal) panes. Only values that are genuinely identical in both panes live here; pane-specific overrides (e.g. center's concurrent-tab backpressure or sidebar's single-terminal buffer sizing) stay local to their config files and are documented there with a one-line reason.

View Source
const FullPaneCaptureQuietWindow = 2 * time.Second

FullPaneCaptureQuietWindow is how long a session must be free of recent activity before a pre-attach full-pane bootstrap snapshot is taken.

View Source
const OverflowLogThrottle = 2 * time.Second

OverflowLogThrottle bounds how often a sustained PTY overflow logs.

Variables

This section is empty.

Functions

func BootstrapSnapshotStillMatchesSession

func BootstrapSnapshotStillMatchesSession(
	sessionName string,
	bootstrap SessionBootstrapCapture,
	opts tmux.Options,
	fns SessionBootstrapFns,
) bool

func CaptureSessionHistory

func CaptureSessionHistory(
	sessionName string,
	fallbackCols, fallbackRows int,
	opts tmux.Options,
	fns SessionBootstrapFns,
	capturePane func(string, tmux.Options) ([]byte, error),
) ([]byte, int, int)

func DrainKnownPTYNoiseTrailing

func DrainKnownPTYNoiseTrailing(trailing *[]byte) []byte

DrainKnownPTYNoiseTrailing flushes any carried line fragment at stream end.

func FilterKnownPTYNoiseStream

func FilterKnownPTYNoiseStream(data []byte, trailing *[]byte) []byte

FilterKnownPTYNoiseStream filters chunked PTY output while carrying a possible trailing diagnostic fragment between chunks so split lines can be removed.

func ForwardPTYMsgs

func ForwardPTYMsgs(msgCh <-chan tea.Msg, sink func(tea.Msg), merger OutputMerger)

ForwardPTYMsgs reads from msgCh, merges consecutive output messages, forwards via sink.

func ResizeTerminalForSessionRestore

func ResizeTerminalForSessionRestore(term *vterm.VTerm, cols, rows int)

ResizeTerminalForSessionRestore ensures a reused local VTerm matches the PTY dimensions before replaying any tmux capture into it.

func RestorePaneCapture

func RestorePaneCapture(term *vterm.VTerm, c SessionRestoreCapture, currentCols, currentRows int)

func RestoreScrollbackCapture

func RestoreScrollbackCapture(
	term *vterm.VTerm,
	data []byte,
	captureCols, captureRows int,
	currentCols, currentRows int,
)

func RollbackExistingSessionBootstrap

func RollbackExistingSessionBootstrap(sessionName string, bootstrap SessionBootstrapCapture, opts tmux.Options, fns SessionBootstrapFns)

func RunPTYReader

func RunPTYReader(
	r io.Reader, msgCh chan tea.Msg, cancel <-chan struct{},
	heartbeat *int64, cfg PTYReaderConfig, factory PTYMsgFactory,
)

RunPTYReader reads from r, buffers bytes, sends Output messages via msgCh on ticker ticks or when MaxPendingBytes is hit. Sends Stopped on error. msgCh is closed exactly once, by this goroutine, on every return path (including panic) via the deferred close below, so ForwardPTYMsgs never blocks on a channel that will not close.

func SendPTYMsg

func SendPTYMsg(msgCh chan tea.Msg, cancel <-chan struct{}, msg tea.Msg) bool

SendPTYMsg sends msg on msgCh, returning false if cancel fires first.

func SessionBootstrapExclusive

func SessionBootstrapExclusive(sessionName string, quietWindow time.Duration, opts tmux.Options, fns SessionBootstrapFns) bool

func SessionBootstrapGeneration

func SessionBootstrapGeneration(sessionName string, opts tmux.Options, fns SessionBootstrapFns) (int64, string, bool)

func SessionHistoryCaptureSize

func SessionHistoryCaptureSize(sessionName string, fallbackCols, fallbackRows int, opts tmux.Options, fns SessionBootstrapFns) (int, int)

func SessionRestorePaneModeState

func SessionRestorePaneModeState(mode tmux.PaneModeState) vterm.PaneModeState

func SessionSnapshotSize

func SessionSnapshotSize(captureFullPane bool, snapshotCols, snapshotRows, fallbackCols, fallbackRows int) (int, int)

func TrimOverflow

func TrimOverflow(pending []byte, maxBuffered int, seed vterm.ParserCarryState) (retained []byte, carry vterm.ParserCarryState, droppedFromFront int)

TrimOverflow applies the overflow policy to a pending buffer that exceeded maxBuffered: it drops the overflow prefix at a parser-safe boundary (seeded with the terminal's current parser carry state) and returns a fresh copy of the retained bytes, the carry for the next chunk, and how many bytes were dropped from the front.

func TrimPTYOverflowPrefix

func TrimPTYOverflowPrefix(data []byte, drop int, seed vterm.ParserCarryState) ([]byte, vterm.ParserCarryState)

TrimPTYOverflowPrefix drops at least drop bytes from a buffered PTY stream and, when the cut lands inside an ANSI/control sequence or UTF-8 rune, advances to the next parser-safe boundary. This avoids rendering the tail of a truncated control sequence as visible text after overflow backpressure.

Parser continuity is modeled by vterm.AdvanceParserCarryState — the single shared chunk-boundary state machine — so trimming can never disagree with the terminal parser about where a sequence ends.

Types

type AppendResult

type AppendResult struct {
	// Data is the incoming data after any overflow carry was consumed — the
	// bytes actually appended to PendingOutput.
	Data []byte
	// PrevPendingLen is len(PendingOutput) before this chunk was appended.
	PrevPendingLen int
	// RetainedStart is the absolute offset (relative to the pre-trim buffer) of
	// the first retained byte after an overflow trim; 0 when Overflowed is false.
	RetainedStart int
	// Overflowed reports whether the buffer exceeded maxBuffered and was trimmed.
	Overflowed bool
}

AppendResult reports what AppendOutput did so a pane can run its own post-append accounting (center's chat-activity slice tracking).

type OutputHooks

type OutputHooks struct {
	// OnCarryConsumed runs under mu when a pending overflow carry was consumed
	// from the front of the incoming data (center resets its activity ANSI
	// state here).
	OnCarryConsumed func()
	// AfterAppendLocked runs under mu right after data is appended to
	// PendingOutput, receiving the appended byte count. When nil, AppendOutput
	// does not take the lock for it (the sidebar has no per-append accounting).
	AfterAppendLocked func(appendedLen int)
	// SeedForTrim returns the parser carry seed for the overflow trim. It runs
	// with mu released and does its own locking; panes reset their terminal
	// parser state inside it. A nil hook seeds with the zero carry state.
	SeedForTrim func() vterm.ParserCarryState
	// OnOverflowLocked runs under mu during the overflow bookkeeping, after
	// OverflowTrimCarry is stored and before NoteOverflowDropLocked, for
	// pane-specific settle/noise-reset accounting. It receives the dropped
	// overflow byte count, the absolute offset of the first retained byte, and
	// the buffer length before this chunk was appended.
	OnOverflowLocked func(overflow, retainedStart, prevPendingLen int)
	// LogOverflow emits the throttled overflow warning (pane-specific wording)
	// with the aggregated dropped-byte total. It runs with mu released.
	LogOverflow func(droppedTotal int)
	// DropBytesCounter and DropCounter name the per-pane overflow perf counters
	// (e.g. "pty_output_drop_bytes"/"pty_output_drop"). Empty names are skipped.
	DropBytesCounter string
	DropCounter      string
}

OutputHooks carries the pane-specific pieces of AppendOutput. Every hook is optional; a nil hook is skipped, and where noted its lock section is not entered. Hooks documented "under mu" run while AppendOutput holds the pane mutex; the rest run with mu released.

type OutputMerger

type OutputMerger struct {
	ExtractData func(msg tea.Msg) ([]byte, bool)         // type-assert + return Data
	CanMerge    func(current, next tea.Msg) bool         // same workspace+tab?
	Build       func(first tea.Msg, data []byte) tea.Msg // clone with merged data
	MaxPending  int
}

OutputMerger configures how ForwardPTYMsgs merges consecutive output messages.

type PTYMsgFactory

type PTYMsgFactory struct {
	Output  func(data []byte) tea.Msg
	Stopped func(err error) tea.Msg
}

PTYMsgFactory creates tea.Msg values from PTY events. Closures capture the WorkspaceID/TabID from the call site.

type PTYReaderConfig

type PTYReaderConfig struct {
	Label           string // safego goroutine label
	ReadBufferSize  int
	ReadQueueSize   int
	FrameInterval   time.Duration
	MaxPendingBytes int
}

PTYReaderConfig configures the shared PTY read loop.

type ReaderNamespace

type ReaderNamespace struct {
	LabelPrefix     string
	ReadQueueSize   int
	MaxPendingBytes int
}

ReaderNamespace bundles the per-pane naming and queue tuning that differ between the center and sidebar terminal stacks when they start a PTY reader. LabelPrefix ("center"/"sidebar") names the reader goroutines and read-loop perf label; ReadQueueSize and MaxPendingBytes are the pane's read-queue depth and in-flight backpressure ceiling. The shared read-buffer size and frame interval come from the ptyio tuning constants, so they are not part of ns.

type SessionBootstrap

type SessionBootstrap struct {
	Fns         SessionBootstrapFns
	CapturePane func(string, tmux.Options) ([]byte, error)
}

SessionBootstrap bundles a SessionBootstrapFns (and the pane-capture fn) with the bootstrap operations that consume them, so each caller constructs a single instance from its own test-seam vars and invokes the operations as methods instead of re-declaring a parallel set of package-level wrappers. The instance is value-typed and cheap: callers rebuild it per call from their seam vars so a test override of a seam var flows through the next operation.

func (SessionBootstrap) CaptureExisting

func (s SessionBootstrap) CaptureExisting(sessionName string, cols, rows int, opts tmux.Options) SessionBootstrapCapture

CaptureExisting captures a pre-attach full-pane bootstrap snapshot of an existing session, using the standard full-pane quiet window.

func (SessionBootstrap) CaptureHistory

func (s SessionBootstrap) CaptureHistory(sessionName string, fallbackCols, fallbackRows int, opts tmux.Options) ([]byte, int, int)

CaptureHistory captures the session scrollback plus its capture dimensions.

func (SessionBootstrap) HistoryCaptureSize

func (s SessionBootstrap) HistoryCaptureSize(sessionName string, fallbackCols, fallbackRows int, opts tmux.Options) (int, int)

HistoryCaptureSize resolves the capture dimensions for a history fallback.

func (SessionBootstrap) Rollback

func (s SessionBootstrap) Rollback(sessionName string, bootstrap SessionBootstrapCapture, opts tmux.Options)

Rollback restores the pane size mutated while capturing the bootstrap snapshot.

func (SessionBootstrap) SnapshotStillMatches

func (s SessionBootstrap) SnapshotStillMatches(sessionName string, bootstrap SessionBootstrapCapture, opts tmux.Options) bool

SnapshotStillMatches reports whether the captured bootstrap snapshot is still authoritative for the session.

type SessionBootstrapCapture

type SessionBootstrapCapture struct {
	Snapshot         tmux.PaneSnapshot
	CaptureFullPane  bool
	SnapshotCaptured time.Time
	SessionCreatedAt int64
	PaneID           string
	RollbackCols     int
	RollbackRows     int
	NeedsRollback    bool
}

func CaptureExistingSessionBootstrap

func CaptureExistingSessionBootstrap(
	sessionName string,
	cols, rows int,
	quietWindow time.Duration,
	opts tmux.Options,
	fns SessionBootstrapFns,
) SessionBootstrapCapture

type SessionBootstrapFns

type SessionBootstrapFns struct {
	SessionHasClients       func(string, tmux.Options) (bool, error)
	SessionClientCount      func(string, tmux.Options) (int, error)
	SessionActiveWithin     func(string, time.Duration, tmux.Options) (bool, error)
	SessionLatestActivity   func(string, tmux.Options) (time.Time, bool, error)
	SessionCreatedAt        func(string, tmux.Options) (int64, error)
	SessionPaneID           func(string, tmux.Options) (string, error)
	SessionPaneSnapshotInfo func(string, tmux.Options) (int, int, bool, error)
	SessionPaneSize         func(string, tmux.Options) (int, int, bool, error)
	ResizePaneToSize        func(string, int, int, tmux.Options) error
	CapturePaneSnapshot     func(string, tmux.Options) (tmux.PaneSnapshot, error)
}

type SessionRestoreCapture

type SessionRestoreCapture struct {
	ScrollbackCapture           []byte
	PostAttachScrollbackCapture []byte
	CaptureFullPane             bool
	SnapshotCols                int
	SnapshotRows                int
	SnapshotCursorX             int
	SnapshotCursorY             int
	SnapshotHasCursor           bool
	SnapshotModeState           tmux.PaneModeState
}

SessionRestoreCapture is the pane snapshot captured at (re)attach time and replayed into a fresh vterm by RestorePaneCapture.

Invariant: a new snapshot field is a one-line addition here plus the producer struct literals — the message structs embed this type, so the field is promoted everywhere without touching consumer signatures.

type StartReaderOptions

type StartReaderOptions struct {
	// AcquireTerm is evaluated under the state lock. It returns the terminal
	// to read from, or nil when the tab/terminal is not in a readable state.
	// It must return an untyped nil for "not readable" (not a nil pointer
	// wrapped in the interface).
	AcquireTerm func() io.Reader
	Config      PTYReaderConfig
	Factory     PTYMsgFactory
	// ReaderLabel and ForwardLabel name the spawned goroutines.
	ReaderLabel  string
	ForwardLabel string
	// Forward drains the reader's message channel into the UI sink.
	Forward func(<-chan tea.Msg)
}

StartReaderOptions bundles the per-consumer pieces of starting a PTY read loop on a shared State: how to get the terminal, how messages are built, and how they are forwarded into the UI.

func StartReaderOptionsFor

func StartReaderOptionsFor(ns ReaderNamespace, acquire func() io.Reader, factory PTYMsgFactory, forward func(<-chan tea.Msg)) StartReaderOptions

StartReaderOptionsFor builds the StartReaderOptions both panes pass to StartReader, filling in the shared read-buffer size and frame interval and the "<prefix>.pty_read_loop/reader/forward" goroutine labels from ns. The pane-specific terminal accessor, message factory, and forwarding sink are supplied by the caller.

type State

type State struct {
	// PendingOutput buffers raw PTY output between flush ticks so partial
	// screen updates are not rendered.
	PendingOutput []byte
	// NoiseTrailing carries an incomplete known-noise line fragment (e.g. a
	// macOS malloc diagnostic) across chunk boundaries.
	NoiseTrailing []byte
	// OverflowTrimCarry is the parser carry state at the last overflow cut.
	OverflowTrimCarry vterm.ParserCarryState
	// FlushScheduled marks that a flush tick is already pending.
	FlushScheduled bool
	// LastOutputAt is when PTY data last arrived.
	LastOutputAt time.Time
	// FlushPendingSince is when the currently-scheduled flush was requested.
	FlushPendingSince time.Time
	// LastOverflowLogAt and OverflowDroppedSinceLog throttle the overflow-drop
	// warning so a sustained overflow logs at most once per throttle window
	// with the aggregated byte count.
	LastOverflowLogAt       time.Time
	OverflowDroppedSinceLog int

	// MsgCh is the reader goroutine's output channel; ReaderCancel signals it
	// to stop. ReaderActive guards against starting two readers.
	MsgCh        chan tea.Msg
	ReaderCancel chan struct{}
	ReaderActive bool
	// ReaderGen identifies the current reader goroutine. StartReader increments
	// it; a reader's exit cleanup only clears state when its generation is
	// still current, so a slow-exiting stale reader cannot clobber its
	// replacement's bookkeeping. Guarded by the embedder's mutex.
	ReaderGen uint64
	// Heartbeat is the last reader read time in nanoseconds. Atomic.
	Heartbeat int64

	// RestartBackoff/RestartCount/RestartSince implement exponential backoff
	// for reader restarts within a rolling window.
	RestartBackoff time.Duration
	RestartCount   int
	RestartSince   time.Time

	// CachedSnap/CachedVersion/CachedShowCursor cache the rendered terminal
	// snapshot so an unchanged terminal does not rebuild it every frame.
	CachedSnap       *compositor.VTermSnapshot
	CachedVersion    uint64
	CachedShowCursor bool
	SnapshotBuffer   compositor.SnapshotDoubleBuffer
}

State holds the PTY I/O bookkeeping shared by the center tab and sidebar terminal stacks: pending-output buffering, flush scheduling, overflow-trim carry, reader lifecycle, restart backoff, and the rendered-snapshot cache.

It is embedded by both consumers. Locking stays with the embedding struct: unless a field is documented as atomic, it must be accessed under the embedder's mutex or from the single-writer Update goroutine, exactly as the fields were before they moved here.

func (*State) AppendOutput

func (st *State) AppendOutput(mu sync.Locker, data []byte, maxBuffered int, h OutputHooks) AppendResult

AppendOutput buffers a chunk of PTY output on st, applying the shared overflow-trim policy, and returns what it did for pane-specific follow-up. It consumes any pending overflow carry, appends to PendingOutput, and — when the buffer exceeds maxBuffered — drops a parser-safe prefix, stores the new carry, and emits the pane's drop counters and throttled warning.

mu is the embedding struct's mutex. AppendOutput acquires and releases it on exactly the boundaries the two panes used before this became shared: a lock around the carry consume, an optional lock for AfterAppendLocked, the SeedForTrim hook doing its own locking, and a lock around the overflow bookkeeping. PendingOutput is written with mu released, so call AppendOutput only from the single-writer Update goroutine, as both panes do.

func (*State) CachedSnapshotLayerLocked

func (st *State) CachedSnapshotLayerLocked(term *vterm.VTerm, version uint64, showCursor bool) *compositor.VTermLayer

CachedSnapshotLayerLocked returns a VTermLayer for term, reusing the cached snapshot when the terminal version and cursor visibility are both unchanged and otherwise taking a fresh double-buffered snapshot. It emits the shared vterm_snapshot_cache_hit/miss counters and refreshes the snapshot cache. It returns nil when the double buffer cannot build a snapshot. The caller must hold the pane mutex (the same lock held when snapshotting the VTerm).

This is the shared non-chat snapshot skeleton used by both the sidebar terminal and the center pane. Center wraps it with chat-tab cursor post-processing on its own; only the non-chat arm routes here.

func (*State) ConsumeOverflowCarryLocked

func (st *State) ConsumeOverflowCarryLocked(data []byte) ([]byte, bool)

ConsumeOverflowCarryLocked resumes a previous overflow cut: when carry state is pending from the last trim, it advances data past the unsafe prefix and updates the carry. It returns the (possibly trimmed) data and whether a carry was consumed. The caller must hold the state lock.

func (*State) DecidePTYRestartLocked

func (st *State) DecidePTYRestartLocked(termAlive bool, window time.Duration, maxRestarts int) (restart bool, backoff time.Duration)

DecidePTYRestartLocked computes the shared restart-vs-detach decision after a PTY reader stops. It owns only the policy — panes keep their own side effects (messages, tab/terminal field writes) around the returned decision.

termAlive reports whether the underlying terminal is still open. When it is, the restart budget is advanced via NextRestartBackoffLocked(window, maxRestarts): restart==true means the caller should schedule a restart after backoff; restart==false means the budget is exhausted and the caller should apply its detach side effects (RestartBackoff has already been zeroed by the budget check, matching the historical panes, while RestartCount/RestartSince keep the window). When termAlive is false the helper calls ResetRestartBackoffLocked — restarts no longer apply — and returns restart==false so the caller detaches.

The caller must hold the state lock (same convention as NextRestartBackoffLocked), and should apply its detach side effects under that same critical section before releasing it.

func (*State) FlushDelay

func (st *State) FlushDelay(now time.Time, quiet, maxInterval time.Duration) (time.Duration, bool)

FlushDelay implements the flush quiet-period policy: a flush is deferred while output is still arriving (quietFor < quiet) unless it has already been pending longer than maxInterval. It returns the delay before the next flush attempt and whether the flush should be deferred.

func (*State) FlushGate

func (st *State) FlushGate(now time.Time, quiet, maxInterval time.Duration) (deferDelay time.Duration, deferred bool)

FlushGate applies the shared flush quiet-period policy at the top of a flush tick. When output is still arriving it marks a flush scheduled and returns (delay, true) so the caller re-arms after delay; otherwise it clears the scheduled flags and returns (0, false) so the caller proceeds to flush the buffer. FlushScheduled/FlushPendingSince follow the same single-writer (Update goroutine) convention the two panes used before this was shared.

func (*State) MarkReaderStopped

func (st *State) MarkReaderStopped(mu sync.Locker, gen uint64)

MarkReaderStopped clears reader bookkeeping after the read loop has exited on its own (RunPTYReader returned), but only when gen is still the current reader generation. A stale reader that exits after a restart must not clobber the replacement reader's state. The cancel channel is intentionally left in place for the next StartReader to close.

func (*State) NextRestartBackoffLocked

func (st *State) NextRestartBackoffLocked(window time.Duration, maxRestarts int) (backoff time.Duration, ok bool)

NextRestartBackoffLocked advances the restart-backoff state and returns the delay before the next restart attempt. ok is false once more than maxRestarts attempts happened inside the rolling window — the caller should give up instead of restarting. The caller must hold the state lock.

func (*State) NoteOverflowDropLocked

func (st *State) NoteOverflowDropLocked(droppedBytes int) (logNow bool, total int)

NoteOverflowDropLocked accumulates dropped overflow bytes and reports whether a throttled overflow warning should be emitted now (the caller logs outside the lock). It returns the aggregated dropped-byte total to report when logNow is true. The caller must hold the state lock.

func (*State) ReaderStalled

func (st *State) ReaderStalled(mu sync.Locker, stallTimeout time.Duration) bool

ReaderStalled reports whether an active reader's heartbeat is older than stallTimeout. mu must not be held by the caller.

func (*State) RearmFlush

func (st *State) RearmFlush(now time.Time, onDrained func()) (rearm bool)

RearmFlush finishes a flush pass after a chunk was written: if PendingOutput drained it truncates the buffer, runs onDrained for pane-specific bookkeeping, and returns false; otherwise it marks a follow-up flush scheduled at now and returns true so the caller re-arms its own tick (the re-arm delay and message are pane-specific and stay with the caller). onDrained may be nil and, when set, is expected to take the pane mutex for the bookkeeping it clears — it is called with mu released, matching the prior per-pane code.

func (*State) ResetRestartBackoffLocked

func (st *State) ResetRestartBackoffLocked()

ResetRestartBackoffLocked clears restart-backoff state, e.g. when the terminal is detached and restarts no longer apply. The caller must hold the state lock.

func (*State) ResetSnapshotCache

func (s *State) ResetSnapshotCache()

ResetSnapshotCache clears cached render snapshots and the reusable snapshot buffers.

func (*State) StartReader

func (st *State) StartReader(mu sync.Locker, opts StartReaderOptions)

StartReader starts the PTY read loop for st unless one is already running. mu is the embedding struct's mutex (the one documented to guard st). The reader marks st stopped (under mu) when it exits.

func (*State) StopReader

func (st *State) StopReader(mu sync.Locker)

StopReader signals the read loop to stop and clears reader bookkeeping. mu must not be held by the caller.

func (*State) TakeFlushChunkLocked

func (st *State) TakeFlushChunkLocked(maxChunk int) []byte

TakeFlushChunkLocked removes up to maxChunk bytes from the front of PendingOutput and returns a copy (nil when nothing is buffered). A non-positive maxChunk takes the whole buffer. The caller must hold the state lock.

func (*State) WriteFilteredChunkLocked

func (st *State) WriteFilteredChunkLocked(write func([]byte), chunk []byte) []byte

WriteFilteredChunkLocked filters chunk for known PTY noise (carrying incomplete fragments in NoiseTrailing), writes the visible remainder via write, and emits the per-flush perf counters. It returns the filtered bytes. The caller must hold the state lock; write goes to the terminal guarded by that same lock.

Jump to

Keyboard shortcuts

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