class

TermBuf::Input::Stream

Inherits Reference < Object

Everything a terminal has to say, as events.

Two fibres. Reader reads the device and puts what it read on a channel; a dispatcher takes it off, feeds the decoder, offers each complete escape sequence to the registered Patterns, and sends whatever came of it to the application. The split is what lets a blocking read have a thread of its own without the decoder having one too.

The dispatcher is the only fibre that ever touches the decoder or asks a pattern about a sequence, so neither needs locking. Registration is the exception, and Patterns guards itself for it.

Time arrives the same way bytes do. A timer is a fibre that sleeps and then puts a tick on the same channel the reader writes to, so a wake-up takes its place in the queue behind whatever the terminal had already said. The decoder's own escape and paste deadlines are timers like any other; the application's are #after and #cancel.

Between the dispatcher and the channel is #stages, a list of Stages every event walks before the application sees it. Keys, patterns' events, timers and signals all go through it; #inject does not.

Constants

CAPACITY = 256

Events waiting for the application. Once this fills, decoding stops, and after that reading does: the terminal's own buffer then applies backpressure to the keyboard rather than memory growing here.

Constructors

new(io : IO, blocking : Bool)

Builds a stream over io, which nothing is read from until #start.

blocking says whether a read on io blocks the thread it runs on. A terminal does, and is given an execution context to itself so that the read cannot stall the fibres around it; an IO::Memory or a pipe the event loop can wait on does not, and is read from a fibre.

The SGR mouse pattern is registered here, so a report is understood from this moment whether or not this shard asked for the reporting.

Source

Instance methods

after(span : Time::Span) : Nonce

Asks for an Events::Timer in span from now, and returns the nonce that will name it.

The tick comes down the same channel as the bytes, so it is ordered against them: anything the terminal said before this call is delivered before this timer goes off. What it is not is punctual — it arrives no sooner than span, and however much later the application takes to drain the events ahead of it.

Source
cancel(nonce : Nonce) : Nil

Withdraws the timer nonce names. Nothing is delivered for it, even if its fibre had already woken by the time this was called.

Source
close

Stops delivering events.

The reader is left where it is, blocked on a device only the owner of that device can close; it ends when the device does. Nothing it reads after this reaches anyone.

Signal handlers go back to the default: they are process-global, and one left pointing at a stream nobody is draining would fill the inbound channel and then block Crystal's signal fibre.

Source
closed?

Whether the events channel has been closed.

Source
decoder

Turns the bytes into events. Reachable so that its deadlines can be adjusted; feeding it from anywhere but the dispatcher is not safe.

Source
events

Everything the terminal has to say, in the order it happened.

Source
inject(event : Event) : Nil

Sends event without decoding anything, for what the driver has to say on its own account.

Source
patterns

The sequences the application is interested in. Anything arriving from the terminal that one of them claims becomes whatever it returns; everything else is a key someone pressed.

Source
preload(bytes : Bytes) : Nil

Bytes that were read before this stream existed, to be decoded ahead of anything the reader finds.

The capability probe reads whatever the terminal sends during its window, and some of that is a key pressed while the application was starting. It belongs at the front of the stream, not nowhere.

Source
signals

What the operating system has to say. Its handlers are not installed until something calls Signals#install, since traps are process-global and a stream is not necessarily the process.

Source
stages

The chain every event walks on its way to the application. See Stages for how it is changed and why that is safe while events are flowing.

Empty by default. A driver puts its own translations here — termbuf answers SIGWINCH in a stage called :resize, which consumes the signal and sends a resize event in its place — and an application adds, removes or reorders them:

stream.stages.push my_stage

#inject bypasses the chain entirely, since what the driver has to say on its own account is not something a filter should be able to swallow.

Source
start

Starts both fibres.

Source
started?

Whether the fibres are running.

Source
timers

The wake-ups this stream has armed, the application's and the decoder's alike. #after and #cancel are the way in.

Source