class TermBuf::Decoder

Overview

Turns the bytes a terminal sends into events.

Three things arrive on the same stream and have to be told apart: replies to queries the application made, text that was pasted rather than typed, and key presses. The first is settled by ResponseRegistry, since nothing about the bytes says whether a finger or a terminal produced them. The second is settled by the bracketed paste markers. Everything left is a key.

State is carried between calls, because none of the three respects the boundaries of a read: an escape sequence, a UTF-8 character, and a paste can each be split across as many reads as the kernel feels like.

Defined in:

termbuf/input/decoder.cr

Constant Summary

ESCAPE_TIMEOUT = 25.milliseconds

How long to wait for the rest of an escape sequence before deciding there is no rest.

The escape key sends one byte and so does the start of every arrow key, so the two are indistinguishable until either more bytes arrive or enough time passes that none will.

LETTER_KEYS = {'A' => Key::Name::Up, 'B' => Key::Name::Down, 'C' => Key::Name::Right, 'D' => Key::Name::Left, 'H' => Key::Name::Home, 'F' => Key::Name::End, 'E' => Key::Name::Begin, 'P' => Key::Name::F1, 'Q' => Key::Name::F2, 'R' => Key::Name::F3, 'S' => Key::Name::F4}

Arrows, and everything sharing their shape, in the ESC [ 1 ; m x form.

MAX_PASTE = (4 * 1024) * 1024

Where a paste stops being a paste and starts being a denial of service. Reached only by a terminal that sent an opening marker and no closing one.

MINIMUM_DEADLINE = 1.millisecond

Never ask for a read deadline shorter than this. A deadline that has already passed would otherwise become a zero or negative timeout.

PASTE_END = "\e[201~".to_slice
PASTE_NOTICE = 300.milliseconds

How long a paste has to have been arriving before it is worth telling the application about. Measured from the opening marker whether or not anything followed it.

PASTE_PROGRESS = 100.milliseconds

How often the byte count is worth resending. A paste large enough to notice arrives in hundreds of reads, and an application draining the channel slowly should not be made to drain hundreds of notices.

PASTE_STALL = 3.seconds

How long a paste may go without a single byte before it is treated as abandoned.

Reset by every byte rather than measured from the opening marker, because the question is whether the paste is slow or stopped and the only evidence either way is whether anything is still arriving. A paste over a link with seconds of latency stays alive as long as it makes progress; one whose closing marker will never come ends here rather than swallowing every keystroke after it.

PASTE_START = "\e[200~".to_slice
SS3_KEYS = {'A' => Key::Name::Up, 'B' => Key::Name::Down, 'C' => Key::Name::Right, 'D' => Key::Name::Left, 'H' => Key::Name::Home, 'F' => Key::Name::End, 'E' => Key::Name::Begin, 'M' => Key::Name::Enter, 'P' => Key::Name::F1, 'Q' => Key::Name::F2, 'R' => Key::Name::F3, 'S' => Key::Name::F4}
TILDE_KEYS = {1 => Key::Name::Home, 2 => Key::Name::Insert, 3 => Key::Name::Delete, 4 => Key::Name::End, 5 => Key::Name::PageUp, 6 => Key::Name::PageDown, 7 => Key::Name::Home, 8 => Key::Name::End, 11 => Key::Name::F1, 12 => Key::Name::F2, 13 => Key::Name::F3, 14 => Key::Name::F4, 15 => Key::Name::F5, 17 => Key::Name::F6, 18 => Key::Name::F7, 19 => Key::Name::F8, 20 => Key::Name::F9, 21 => Key::Name::F10, 23 => Key::Name::F11, 24 => Key::Name::F12, 25 => Key::Name::F13, 26 => Key::Name::F14, 28 => Key::Name::F15, 29 => Key::Name::F16, 31 => Key::Name::F17, 32 => Key::Name::F18, 33 => Key::Name::F19, 34 => Key::Name::F20}

ESC [ n ~, where n names the key. The gaps are where DEC left room for keys nobody built.

Constructors

Instance Method Summary

Constructor Detail

def self.new(responses : ResponseRegistry = ResponseRegistry.new) #

[View source]

Instance Method Detail

def decode(bytes : Bytes) : Key #

What one complete escape sequence means.


[View source]
def escape_timeout : Time::Span #

See ESCAPE_TIMEOUT. Worth raising over a slow link, where a sequence can take longer than that to arrive in full.


[View source]
def escape_timeout=(escape_timeout : Time::Span) #

See ESCAPE_TIMEOUT. Worth raising over a slow link, where a sequence can take longer than that to arrive in full.


[View source]
def feed(bytes : Bytes, &emit : Event -> ) : Nil #

Feeds bytes in, yielding whatever they completed.


[View source]
def flush(&emit : Event -> ) : Nil #

Gives up waiting and delivers what is held back for what it is: an escape that begins nothing is the escape key, and a truncated character is a broken one.


[View source]
def paste_notice : Time::Span #

[View source]
def paste_notice=(paste_notice : Time::Span) #

[View source]
def paste_progress : Time::Span #

[View source]
def paste_progress=(paste_progress : Time::Span) #

[View source]
def paste_stall : Time::Span #

[View source]
def paste_stall=(paste_stall : Time::Span) #

[View source]
def pasting? : Bool #

Whether a paste is open, so that text is being collected rather than delivered as keys.


[View source]
def pending? : Bool #

Whether anything is being held back for want of more bytes.

What the escape timeout is for: a lone escape looks exactly like the start of an arrow key until enough time passes that no arrow key is coming.


[View source]
def read_deadline : Time::Span | Nil #

How long the reader may wait before calling #tick, or nil when it may block until something arrives.

Every piece of held state is a bet that more bytes are coming, and each one needs its losing case. With nothing held and no paste open there is nothing to time, so an idle application costs nothing.


[View source]
def tick(&emit : Event -> ) : Nil #

Called when a read deadline expires. Works out which one it was.


[View source]