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
- func BootstrapSnapshotStillMatchesSession(sessionName string, bootstrap SessionBootstrapCapture, opts tmux.Options, ...) bool
- func CaptureSessionHistory(sessionName string, fallbackCols, fallbackRows int, opts tmux.Options, ...) ([]byte, int, int)
- func DrainKnownPTYNoiseTrailing(trailing *[]byte) []byte
- func FilterKnownPTYNoiseStream(data []byte, trailing *[]byte) []byte
- func ForwardPTYMsgs(msgCh <-chan tea.Msg, sink func(tea.Msg), merger OutputMerger)
- func ResizeTerminalForSessionRestore(term *vterm.VTerm, cols, rows int)
- func RestorePaneCapture(term *vterm.VTerm, c SessionRestoreCapture, currentCols, currentRows int)
- func RestoreScrollbackCapture(term *vterm.VTerm, data []byte, captureCols, captureRows int, ...)
- func RollbackExistingSessionBootstrap(sessionName string, bootstrap SessionBootstrapCapture, opts tmux.Options, ...)
- func RunPTYReader(r io.Reader, msgCh chan tea.Msg, cancel <-chan struct{}, heartbeat *int64, ...)
- func SendPTYMsg(msgCh chan tea.Msg, cancel <-chan struct{}, msg tea.Msg) bool
- func SessionBootstrapExclusive(sessionName string, quietWindow time.Duration, opts tmux.Options, ...) bool
- func SessionBootstrapGeneration(sessionName string, opts tmux.Options, fns SessionBootstrapFns) (int64, string, bool)
- func SessionHistoryCaptureSize(sessionName string, fallbackCols, fallbackRows int, opts tmux.Options, ...) (int, int)
- func SessionRestorePaneModeState(mode tmux.PaneModeState) vterm.PaneModeState
- func SessionSnapshotSize(captureFullPane bool, ...) (int, int)
- func TrimOverflow(pending []byte, maxBuffered int, seed vterm.ParserCarryState) (retained []byte, carry vterm.ParserCarryState, droppedFromFront int)
- func TrimPTYOverflowPrefix(data []byte, drop int, seed vterm.ParserCarryState) ([]byte, vterm.ParserCarryState)
- type AppendResult
- type OutputHooks
- type OutputMerger
- type PTYMsgFactory
- type PTYReaderConfig
- type ReaderNamespace
- type SessionBootstrap
- func (s SessionBootstrap) CaptureExisting(sessionName string, cols, rows int, opts tmux.Options) SessionBootstrapCapture
- func (s SessionBootstrap) CaptureHistory(sessionName string, fallbackCols, fallbackRows int, opts tmux.Options) ([]byte, int, int)
- func (s SessionBootstrap) HistoryCaptureSize(sessionName string, fallbackCols, fallbackRows int, opts tmux.Options) (int, int)
- func (s SessionBootstrap) Rollback(sessionName string, bootstrap SessionBootstrapCapture, opts tmux.Options)
- func (s SessionBootstrap) SnapshotStillMatches(sessionName string, bootstrap SessionBootstrapCapture, opts tmux.Options) bool
- type SessionBootstrapCapture
- type SessionBootstrapFns
- type SessionRestoreCapture
- type StartReaderOptions
- type State
- func (st *State) AppendOutput(mu sync.Locker, data []byte, maxBuffered int, h OutputHooks) AppendResult
- func (st *State) CachedSnapshotLayerLocked(term *vterm.VTerm, version uint64, showCursor bool) *compositor.VTermLayer
- func (st *State) ConsumeOverflowCarryLocked(data []byte) ([]byte, bool)
- func (st *State) DecidePTYRestartLocked(termAlive bool, window time.Duration, maxRestarts int) (restart bool, backoff time.Duration)
- func (st *State) FlushDelay(now time.Time, quiet, maxInterval time.Duration) (time.Duration, bool)
- func (st *State) FlushGate(now time.Time, quiet, maxInterval time.Duration) (deferDelay time.Duration, deferred bool)
- func (st *State) MarkReaderStopped(mu sync.Locker, gen uint64)
- func (st *State) NextRestartBackoffLocked(window time.Duration, maxRestarts int) (backoff time.Duration, ok bool)
- func (st *State) NoteOverflowDropLocked(droppedBytes int) (logNow bool, total int)
- func (st *State) ReaderStalled(mu sync.Locker, stallTimeout time.Duration) bool
- func (st *State) RearmFlush(now time.Time, onDrained func()) (rearm bool)
- func (st *State) ResetRestartBackoffLocked()
- func (s *State) ResetSnapshotCache()
- func (st *State) StartReader(mu sync.Locker, opts StartReaderOptions)
- func (st *State) StopReader(mu sync.Locker)
- func (st *State) TakeFlushChunkLocked(maxChunk int) []byte
- func (st *State) WriteFilteredChunkLocked(write func([]byte), chunk []byte) []byte
Constants ¶
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.
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.
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.
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 DrainKnownPTYNoiseTrailing ¶
DrainKnownPTYNoiseTrailing flushes any carried line fragment at stream end.
func FilterKnownPTYNoiseStream ¶
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 ¶
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 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 ¶
SendPTYMsg sends msg on msgCh, returning false if cancel fires first.
func SessionRestorePaneModeState ¶
func SessionRestorePaneModeState(mode tmux.PaneModeState) vterm.PaneModeState
func SessionSnapshotSize ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
ReaderStalled reports whether an active reader's heartbeat is older than stallTimeout. mu must not be held by the caller.
func (*State) RearmFlush ¶
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 ¶
StopReader signals the read loop to stop and clears reader bookkeeping. mu must not be held by the caller.
func (*State) TakeFlushChunkLocked ¶
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 ¶
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.